package top.healthylife.gateway.mgr.component;

import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import top.healthylife.basics.utils.exception.BusinessException;
import top.healthylife.basics.utils.utils.IdGenerator;
import top.healthylife.basics.utils.utils.R;
import top.healthylife.basics.utils.utils.RedisUtil;
import top.healthylife.basics.utils.utils.SystemThreadLocal;
import top.healthylife.gateway.dto.request.base.SystemInfoDto;
import top.healthylife.gateway.mgr.service.GatewayAccountService;
import top.healthylife.gateway.mgr.service.GatewayLogService;
import top.heathylife.gateway.common.cons.SystemCons;
import top.heathylife.gateway.common.po.GatewayAccountPo;
import top.heathylife.gateway.common.po.GatewayLogPo;
import top.heathylife.gateway.common.po.SystemPo;
import top.heathylife.gateway.common.utils.SecretJsonUtils;

import java.net.URLEncoder;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * @author maoxiaomeng
 */
@Slf4j
@Component
@AllArgsConstructor
public class ApiComponent {

    GatewayLogService gatewayLogService;
    GatewaySaveLogComponent gatewaySaveLogComponent;
    GatewayAccountService accountService;
    RestTemplate restTemplate;

    /**
     * 根据对象的查询条件获取最新的一条数据
     * 如果MD5给空，返回最新的一条数据
     *
     * @param apiName        接口名称
     * @param md5            请求参数MD5加密值 md5可以为空
     * @param shortName      服务名简称
     * @param periodValidity 有效期
     * @return java.lang.Object
     * @author maoxiaomeng
     * @date 2020/3/31 15:05
     */
    public Object cache(Long periodValidity, String shortName, String apiName, String md5) {
        //获取缓存
        GatewayLogPo bean = new GatewayLogPo()
                .setShortName(shortName)
                .setApiName(apiName)
                //服务即使不在线也可查询
                .setStatus(true)
                //非缓存数据
                .setHitCache(false);
        if (Optional.ofNullable(md5).isPresent()) {
            bean.setMd5(md5);
        }
        return getCache(periodValidity, bean);
    }

    /**
     * 获取接口调用历史
     *
     * @return java.lang.Object
     * @author maoxiaomeng
     * @date 2020/3/20 18:40
     */
    @SneakyThrows
    protected Object getCache(Long periodValidity, GatewayLogPo logPo) {
        //查看是否存在已经调用过的数据,不校验secret，不消耗钱的方式
        //查找历史的数据
        //返回最近一次 相同参数   且正常返回 调用结果
        LambdaQueryWrapper<GatewayLogPo> wrapper = Wrappers.lambdaQuery(logPo)
                .eq(logPo.getMd5() != null, GatewayLogPo::getMd5, logPo.getMd5())
                .eq(GatewayLogPo::getStatus, true)
                .orderByDesc(GatewayLogPo::getCreateTime).last(" limit 1");
        GatewayLogPo functionApiCallRecordPo = gatewayLogService.getOne(wrapper);

        if (ObjectUtil.isNotNull(functionApiCallRecordPo)) {
            //如果传递时间为0.则必定走历史缓存数据
            String returnData = functionApiCallRecordPo.getReturnData();
            if (periodValidity.equals(0L)) {
                log.info("传递时间为0,走历史缓存数据");
                return handleHitCacheField(returnData);
            }
            //如果当前时间和历史时间未超过传递时间,直接使用历史的结果
            long duration = Math.abs(Duration.between(functionApiCallRecordPo.getCreateTime(), LocalDateTime.now()).toMillis());
            if (duration <= periodValidity * 1000) {
                log.info("本次请求,能够接受的的历史缓存数据为:{}秒内的数据", periodValidity);
                return handleHitCacheField(returnData);
            }
        }
        //走真实的接口信息
        return null;
    }

    public static Object handleHitCacheField(String returnData) {
        if (ObjectUtil.isEmpty(returnData)) {
            return null;
        }
        JSONObject jsonObject = JSONObject.parseObject(returnData);
        JSONObject data = jsonObject.getJSONObject("data");
        if (data != null) {
            data.put("hitCache", true);
        }
        return jsonObject;
    }


    /**
     * 获取缓存中的对象
     *
     * @param shortName 接口API的简称
     * @return com.hare.service.function.callapi.po.System Po
     * @author maoxiaomeng
     * @date 2020/3/20 17:58
     */
    public SystemPo checkSystemPo(String shortName) {
        String redisKey = SystemCons.systemHealthKey(shortName);
        if (RedisUtil.exists(redisKey)) {
            return RedisUtil.get(redisKey, SystemPo.class);
        } else {
            log.error("服务不在线:{}", shortName);
            throw new BusinessException("服务不在线");
        }
    }

    /**
     * 调用下游的服务
     *
     * @param secret          请求的帐号
     * @param apiName         接口的名称
     * @param accountJson     账号的json
     * @param applicationName 请求的系统的服务名
     * @param data            请求数据
     * @param gatewayLogPo    初始化日志
     * @return void
     * @author maoxiaomeng
     * @date 2020/3/31 15:12
     */
    public Object callService(String secret, String apiName, String accountJson, String applicationName, Map<String, Object> data, GatewayLogPo gatewayLogPo) {
        //重置ID为空，防止递归后自动生成ID导致冲突
        gatewayLogPo.setId(null);
        //生成流水
        long no = IdGenerator.getId();
        gatewayLogPo.setNo(no);
        try {
            HttpHeaders headers = new HttpHeaders();
            headers.add("secret", secret);
            headers.add("no", String.valueOf(no));
            headers.add("apiName", URLEncoder.encode(apiName, "utf-8"));
            headers.add("account", accountJson);
            //由于使用时restTemplate,使用lb协议     http:服务名/api/unify
            long l1 = System.currentTimeMillis();
            R<Map<String, Object>> r = restTemplate.postForObject(SystemCons.getSystemApiUrl(applicationName),
                    new HttpEntity<>(data,
                            headers), R.class);

            long l2 = System.currentTimeMillis();
            log.info("网关请求接口模块耗时:{}", l2 - l1);
            log.debug("接口返回为:{}", JSONObject.toJSONString(r));
            if (r == null) {
                SystemThreadLocal.set("exception", "下游服务调用异常,无返回");
                return null;
            }
            Map<String, Object> data1 = r.getData();
            Object metadata = null;
            Object reqMetadata = null;
            if (data1 != null) {
                metadata = data1.remove("metadata");
                reqMetadata = data1.remove("reqMetadata");
                data1.put("hitCache", false);
            }

            gatewayLogPo.setReturnData(JSONObject.toJSONString(r));
            //获取三方元数据
            gatewayLogPo.setOriginData(metadata == null ? null : metadata.toString());
            gatewayLogPo.setOriginReqData(reqMetadata == null ? null : reqMetadata.toString());
            if (r.getCode() == 0) {
                gatewayLogPo.setStatus(true);
                return data1;
            } else {
                SystemThreadLocal.set("exception", r.getMsg());
                gatewayLogPo.setStatus(false);
                gatewayLogPo.setErrorMsg(r.getMsg());
                return null;
            }

        } catch (Exception e) {
            SystemThreadLocal.set("exception", e.getMessage());
            //结果失败
            log.error("服务调用异常", e);
            gatewayLogPo.setErrorMsg(e.getMessage());
            gatewayLogPo.setStatus(false);
            throw new BusinessException("服务调用异常:{}", e.getMessage());
        } finally {
            gatewayLogPo.setHitCache(false);
            gatewayLogPo.setFinishTime(LocalDateTime.now());
            //保存调用日志数据
            //入库记录
            long l4 = System.currentTimeMillis();
            gatewaySaveLogComponent.saveLog(gatewayLogPo);
            long l5 = System.currentTimeMillis();
            log.info("网关日志入库耗时:{}", l5 - l4);
        }
    }

    /**
     * 获取服务的系统参数解释
     *
     * @param build           api调用日志
     * @param applicationName 系统名
     * @return SystemInfoDto
     */
    public List<SystemInfoDto> systemConfigInfo(String applicationName, GatewayLogPo build) {
        //生成流水
        long no = IdGenerator.getId();
        build.setNo(no);
        try {
            R<List<SystemInfoDto>> r = restTemplate.getForObject(SystemCons.getSystemConfigUrl(applicationName), R.class);
            assert r != null;
            return r.getData();
        } catch (Exception e) {
            //结果失败
            log.error("服务调用异常", e);
            build.setStatus(false);
            return null;
        } finally {
            build.setHitCache(false);
            build.setFinishTime(LocalDateTime.now());
            //保存调用日志数据
            gatewaySaveLogComponent.saveLog(build);
        }
    }


    /**
     * 检查调用的时候传递的这个参数是否配置有参数
     *
     * @param secret 帐号唯一表示
     * @return 账号信息
     * @author maoxiaomeng
     * @date 2020/3/31 15:35
     */
    public GatewayAccountPo checkSecret(String secret) {
        GatewayAccountPo riskServiceAccountPo = accountService.getOne(new LambdaQueryWrapper<GatewayAccountPo>()
                .eq(GatewayAccountPo::getSecret, secret).last("limit 1"));
        //如果没有配置服务的帐号,直接报异常
        Optional.ofNullable(riskServiceAccountPo).map(GatewayAccountPo::getSecretJson).orElseThrow(() -> {
            log.error("没有配置数据网关服务帐号,当前请求的服务:当前secret{}", secret);
            return new BusinessException("没有配置数据网关服务帐号");
        });
        if (!riskServiceAccountPo.getStatus()) {
            throw new BusinessException("该账号没有启用");
        }
        //获取配置好的服务的帐号密码
        return riskServiceAccountPo;
    }

    /**
     * 检查用户新增的账号的token 是否可用
     *
     * @param secretJson 帐号json
     * @param secret     帐号唯一表示 UUID
     * @param shortName  系统的简称
     * @return Dict
     * @author maoxiaomeng
     * @date 2020/3/31 15:35
     */
    public Dict checkSecretEnable(String secretJson, String secret, String shortName) {
        //获取配置好的服务的帐号密码
        //加密处理
        secretJson = SecretJsonUtils.encryptBase64(secretJson);
        SystemPo systemPo = checkSystemPo(shortName);
        HttpHeaders headers = new HttpHeaders();
        headers.set("secret", secret);
        headers.set("account", secretJson);
        R<Boolean> r = restTemplate.postForObject(SystemCons.getCheckTokenUrl(systemPo.getApplicationName()), new HttpEntity<>(headers), R.class);
        //简单处理下格式
        Dict dict = new Dict();
        assert r != null;
        dict.set("enable", r.getData());
        dict.set("msg", r.getMsg());
        return dict;
    }

}
